﻿using UnityEngine;
using System.Collections;

public class GameCamera : Singleton<GameCamera>
{
	public float zoomedIn = 3f;
	public float zoomedOut = 10f;
	public float zoomedStart = 5f;
	public float zoomSpeed = 100f;

	public float panSpeedZoomedIn = 1f;
	public float panSpeedZoomedOut = 2f;
	public float panSpeedReturnCamera = 5f;

	public float focusZoom = 2.5f;
	public float focusPanSpeed = 3f;

	private bool firstInput = true;
	private bool mouseDown = false;
	private bool lockCharacter = false;
	private float speed;
	private Vector3 tempPosition = Vector3.forward * -10f;
	private Vector3 inputDirection;
	private Vector3 wantedPosition = new Vector3();
	private Rect floorRect = new Rect();
	private Transform trans;
	private Transform character = null;
	private Camera cam;
	private StateMachine m_SM;

	public enum State
	{
		Idle,
		Focus_In,
		Focus_Idle,
		Focus_Out,
	}

	private void Awake()
	{
		trans = transform;
		InitiativeManager.characterChanged += UpdateCharacter;

		cam = camera;
		cam.rect = CalculateCameraWidth(cam.rect);
		cam.orthographicSize = zoomedStart;

		InitStateMachine();
	}

	private void Start()
	{
		floorRect.width = FloorManager.Instance.tiles.GetLength(0);
		floorRect.height = FloorManager.Instance.tiles.GetLength(1);
	}

	private void InitStateMachine()
	{
		m_SM = new StateMachine();

		m_SM.AddState(State.Idle);
		m_SM.AddCallback(State.Idle, StateMachine.StateEvent.UPDATE, IdleUpdate);
		
		m_SM.AddState(State.Focus_In);
		m_SM.AddCallback(State.Focus_In, StateMachine.StateEvent.ENTER, FocusInEnter);
		m_SM.AddCallback(State.Focus_In, StateMachine.StateEvent.UPDATE, FocusInUpdate);
		
		m_SM.AddState(State.Focus_Idle);
		
		m_SM.AddState(State.Focus_Out);
		m_SM.AddCallback(State.Focus_Out, StateMachine.StateEvent.ENTER, FocusOutEnter);
		m_SM.AddCallback(State.Focus_Out, StateMachine.StateEvent.UPDATE, FocusOutUpdate);
	}

	private void OnDestroy()
	{
		InitiativeManager.characterChanged -= UpdateCharacter;
	}

	private void Update()
	{
		m_SM.Update();

#if UNITY_EDITOR
		cam.rect = CalculateCameraWidth(cam.rect);
#endif
	}

	#region IDLE
	private void IdleUpdate()
	{
		if (Input.GetMouseButtonUp(0))
		{
			mouseDown = false;
			firstInput = true;
		}
		else if (Input.GetMouseButtonDown(0) && CamRectContains(cam.rect, Input.mousePosition))
		{
			mouseDown = true;
		}
		
		if (mouseDown)
		{
			if (!firstInput)
			{
				speed = Mathf.Lerp(panSpeedZoomedIn, panSpeedZoomedOut, (cam.orthographicSize - zoomedIn) / (zoomedOut - zoomedIn));
				
				inputDirection = Vector3.zero;
				foreach (CommonInput.InputEvent evt in CommonInput.Instance.GetAllEvents())
				{
					inputDirection += (cam.ScreenToWorldPoint(evt.m_PrevPosition) - cam.ScreenToWorldPoint(evt.m_Position));
				}
				
				inputDirection /= CommonInput.Instance.GetAllEvents().Count;
			}
			
			firstInput = false;
			if (lockCharacter && inputDirection.sqrMagnitude > 0.01f)
			{
				lockCharacter = false;
			}
		}
		
		if (lockCharacter && character != null)
		{
			wantedPosition.x = character.position.x;
			wantedPosition.y = character.position.y;
			wantedPosition.z = -10f;
			
			tempPosition.x = Mathf.Lerp(trans.position.x, wantedPosition.x, 5f * Time.deltaTime);
			tempPosition.y = Mathf.Lerp(trans.position.y, wantedPosition.y, 5f * Time.deltaTime);
		}
		else
		{
			tempPosition.x += inputDirection.x * speed;
			tempPosition.y += inputDirection.y * speed;
			speed *= 0.9f;
		}
		
		tempPosition = ClampInRect(floorRect, tempPosition);
		trans.position = tempPosition;
		
		#if UNITY_EDITOR
		if (CamRectContains(cam.rect, Input.mousePosition))
		{
			float scroll = Input.GetAxis("Mouse ScrollWheel");
			cam.orthographicSize = Mathf.Clamp(cam.orthographicSize + scroll * Time.deltaTime * zoomSpeed * -1f, zoomedIn, zoomedOut);
		}
		#endif
	}
	
	private bool CamRectContains(Rect _rect, Vector2 _point)
	{
		return _point.x >= _rect.x * Screen.width && _point.x <= (_rect.x + _rect.width) * Screen.width
			&& _point.y >= _rect.y * Screen.height && _point.y <= (_rect.y + _rect.height) * Screen.height;
	}
	
	private Vector3 ClampInRect(Rect _rect, Vector3 _position)
	{
		if (_position.x < floorRect.x)
		{
			_position.x = floorRect.x;
		}
		else if (_position.x > floorRect.x + floorRect.width)
		{
			_position.x = floorRect.x + floorRect.width;
		}
		
		if (_position.y < floorRect.y)
		{
			_position.y = floorRect.y;
		}
		else if (_position.y > floorRect.y + floorRect.height)
		{
			_position.y = floorRect.y + floorRect.height;
		}
		
		return _position;
	}
	#endregion

	#region ZOOM
	public void SetZoom(Transform _target)
	{
		targetPosition = _target.position;
		targetPosition.z = trans.position.z;
		m_SM.SetState(State.Focus_In);
	}

	public void RemoveZoom()
	{
		m_SM.SetState(State.Focus_Out);
	}

	private Vector3 targetPosition;
	private Vector3 initialPosition;
	private Vector3 direction;
	private float prevSize;
	private float initialDistance;
	private float initialSize;
	private float targetZoom;
	private void FocusInEnter()
	{
		initialPosition = trans.position;
		direction = (targetPosition - trans.position).normalized;
		initialDistance = (targetPosition - trans.position).sqrMagnitude;
		initialSize = cam.orthographicSize;
		targetZoom = focusZoom;
	}
	
	private void FocusInUpdate()
	{
		if (!MoveAndZoom())
		{
			m_SM.SetState(State.Focus_Idle);
		}
	}

	private void FocusOutEnter()
	{
		targetPosition = initialPosition;
		targetZoom = initialSize;
		initialPosition = trans.position;
		direction = (targetPosition - trans.position).normalized;
		initialDistance = (targetPosition - trans.position).sqrMagnitude;
		initialSize = cam.orthographicSize;
	}
	
	private void FocusOutUpdate()
	{
		if (!MoveAndZoom())
		{
			m_SM.SetState(State.Idle);
		}
	}
	
	private bool MoveAndZoom()
	{
		Vector3 movement = direction * focusPanSpeed * Time.deltaTime;
		if (Vector3.Dot(direction, targetPosition - (trans.position + movement)) < 0f)
		{
			trans.position = targetPosition;
			return false;
		}
		else
		{
			trans.position += movement;
		}
		
		float distance = (targetPosition - trans.position).sqrMagnitude;
		cam.orthographicSize = targetZoom + (initialSize - targetZoom) * (distance / initialDistance); 
		
		return true;
	}
	#endregion
	
	public void UpdateCharacter()
	{
		lockCharacter = true;
		character = InitiativeManager.Instance.current.transform;
	}

	public void MoveCamera(Character _character)
	{
		lockCharacter = true;
		MoveCamera(_character.transform.position);
	}

	public void MoveCamera(Vector3 _position)
	{
		trans.position = _position;
	}

	private Rect CalculateCameraWidth(Rect rect)
	{
		rect.width = (Screen.width - 195f) / Screen.width;
		return rect;
	}
}
